﻿//TODO: break condition for loop (ESC to stop?)


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO;
using System.Threading;

namespace CSharp.Libs.Media
{
    public class DirectoryScanner : IDisposable
    {
        #region declarations
        private string[] _strarrAllFiles;
        private Dictionary<string, FileHashObject> _dicFileHashes;

        #region events
        public event DirectoryScannerProgressEventDelegate DirectoryScannerProgress;
        protected void ThrowDirectoryScannerProgressEvent(
            DirectoryScannerProgressEventArgs ea)
        {
            if (DirectoryScannerProgress != null)
            {
                DirectoryScannerProgress(this, ea);
            }
        }

        public event BeforeGetFileNamesEventDelegate BeforeGetFileNames;
        protected void ThrowBeforeGetFileNamesEvent()
        {
            if (BeforeGetFileNames != null)
            {
                BeforeGetFileNames(this);
            }
        }

        public event AfterGetFileNamesEventDelegate AfterGetFileNames;
        protected void ThrowAfterGetFileNamesEvent()
        {
            if (AfterGetFileNames != null)
            {
                AfterGetFileNames(this);
            }
        }

        public event BeforeCalculatingFileHashEventDelegate BeforeCalculatingFileHash;
        protected void ThrowBeforeCalculatingFileHash(
            CalculatingFileHashEventArgs ea)
        {
            if (BeforeCalculatingFileHash != null)
            {
                BeforeCalculatingFileHash(this, ea);
            }
        }

        public event AfterCalculatingFileHashEventDelegate AfterCalculatingFileHash;
        protected void ThrowAfterCalculatingFileHash(
            CalculatingFileHashEventArgs ea)
        {
            if (AfterCalculatingFileHash != null)
            {
                AfterCalculatingFileHash(this, ea);
            }
        }
        #endregion
        #endregion



        #region constructor
        public DirectoryScanner()
        {
        }

        #region dispose pattern (http://www.codeproject.com/Articles/15360/Implementing-IDisposable-and-the-Dispose-Pattern-P)
        // some fields that require cleanup
        private bool disposed = false; // to detect redundant calls

        protected virtual void Dispose(bool disposing)
        {
            if (!disposed)
            {
                if (disposing)
                {
                    // clean up managed handles here

                }

                disposed = true;
            }
        }

        public void Dispose()
        {
            Dispose(true);
        }
        #endregion
        #endregion



        #region private functions
        private void _getFileNamesRecursive()
        {
            if (Directory.Exists(DirectoryToScan))
            {
                ThrowBeforeGetFileNamesEvent();
                _strarrAllFiles = Directory.GetFiles(DirectoryToScan, "*.*", SearchOption.AllDirectories);
                ThrowAfterGetFileNamesEvent();
                ThrowDirectoryScannerProgressEvent(new DirectoryScannerProgressEventArgs() { Type = DirectoryScannerProgressTypes.FileCountReceived, FileCount = _strarrAllFiles.Count() });
            }
        }

        private void _getAllFileHashes()
        {
            _getFileNamesRecursive();

            if (_strarrAllFiles.Count() > 0)
            {
                if (_dicFileHashes == null) { _dicFileHashes = new Dictionary<string, FileHashObject>(); }
                if (_dicFileHashes.Count > 0) { _dicFileHashes.Clear(); }

                foreach (string strFullFileName in _strarrAllFiles) {
                    FileHashObject fho = new FileHashObject();
                    fho.FullFileName = strFullFileName;

                    CalculatingFileHashEventArgs ea = new CalculatingFileHashEventArgs() { FullFileName = strFullFileName };
                    ThrowBeforeCalculatingFileHash(ea);

                    FileHashCalculator hashCalc = new FileHashCalculator();
                    hashCalc.FullFileName = fho.FullFileName;
                    hashCalc.CalculateFileHash();

                    fho.FileHash = hashCalc.CalculatedHash;

                    _dicFileHashes.Add(strFullFileName, fho);

                    ea.FileHash = hashCalc.CalculatedHash;
                    ThrowAfterCalculatingFileHash(ea);

                    hashCalc.Dispose();
                    hashCalc = null;
                }

                ThrowDirectoryScannerProgressEvent(new DirectoryScannerProgressEventArgs() { Type = DirectoryScannerProgressTypes.DirectoryScanFinished });
            }
        }
        #endregion



        #region methods
        public void GetFileNamesRecursive()
        {
            _getFileNamesRecursive();
        }

        public void GetFileNamesRecursive(
            string DirectoryFullName)
        {
            DirectoryToScan = DirectoryFullName;
            _getFileNamesRecursive();
        }

        public void GetFileNamesRecursiveAsync()
        {
            ThreadPool.QueueUserWorkItem(arg => _getFileNamesRecursive());
        }

        public void GetFileNamesRecursiveAsync(
            string DirectoryFullName)
        {
            DirectoryToScan = DirectoryFullName;
            ThreadPool.QueueUserWorkItem(arg => _getFileNamesRecursive());
        }

        public void GetAllFileHashesOfDirectoryAsync()
        {
            ThreadPool.QueueUserWorkItem(arg => _getAllFileHashes());
        }
        #endregion



        #region properties
        public string DirectoryToScan { get; set; }

        public string[] AllFiles { get { return _strarrAllFiles; } }
        #endregion
    }



    #region associated enums
    public enum DirectoryScannerProgressTypes
    {
        FileCountReceived = 1,
        CalculatingFileHash,
        FileHashCalculated,
        DirectoryScanFinished,
    }
    #endregion



    #region event stuff
    #region         delegates
    public delegate void DirectoryScannerProgressEventDelegate(object sender, DirectoryScannerProgressEventArgs ea);
    public delegate void BeforeGetFileNamesEventDelegate(object sender);
    public delegate void AfterGetFileNamesEventDelegate(object sender);
    public delegate void BeforeCalculatingFileHashEventDelegate(object sender, CalculatingFileHashEventArgs ea);
    public delegate void AfterCalculatingFileHashEventDelegate(object sender, CalculatingFileHashEventArgs ea);
    #endregion

    #region         event args
    public class DirectoryScannerProgressEventArgs : EventArgs
    {
        public DirectoryScannerProgressEventArgs()
        {
            Filename = "";
        }

        public DirectoryScannerProgressTypes Type { get; set; }
        public int FileCount { get; set; }
        public string Filename { get; set; }
    }

    public class CalculatingFileHashEventArgs : EventArgs
    {
        public CalculatingFileHashEventArgs()
        {
            FullFileName = "";
            FileHash = "";
        }
        public string FullFileName { get; set; }
        public string FileHash { get; set; }
    }
    #endregion
    #endregion



    #region associated classes
    public class FileHashObject
    {
        public string FullFileName { get; set; }
        public string FileHash { get; set; }
    }
    #endregion
}
